/*
 * Erstellt für alle Spieler Nodes mit der Information wie weit die jeweiligen Spieler von dem Node entfernt sind.
 */

package de.hsb.ai.agent.misterx.brain;

import game.Move;
import jade.core.AID;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;

import de.hsb.ai.agent.misterx.Drawer;
import de.hsb.ai.util.map.ScotlandJadeMap;
import de.hsb.ai.util.map.ScotlandJadeMapHelper.ScotlandJadeEdge;


public class GraphAnalyser {

	private HashMap<AID, HashMap<Integer, DangerZone>> _dangerZones;
	public HashMap<AID, Integer> MAXZONES; //gibt die Maximalverfügbaren Zonen die jeweils ein Spieler hat.
	public static int MAXZONESALL = 0;
	
	//als Fallen deklarierte Nodes
	//protected final static int[] TRAP_NODES = {6, 4, 2, 162, 5, 7};
	
	private ScotlandJadeMap _map;
	
	private int _maxZones = 3;
	
	
	public GraphAnalyser(ScotlandJadeMap map) {
		this._map = map;
	}
	
	public class DangerZone {
		public AID _aid;
		public Move _move;
		public int _radius;
		
		public DangerZone(AID aid, Move move, int radius) {
			this._aid = aid;
			this._move = move;
			this._radius = radius;
		}
		
		public String toString() {
			return (this._aid.getLocalName() + ":" + this._move + ":" + "-" + this._radius + "-");
		}
	}
	
	/**
	 * bringt die Nodes datantechnisch auf den neusten Stand.
	 * wird bei jedem Pathfinding einmal aufgerufen.
	 * @param zones
	 */
	public void update(int zones) {
		this._maxZones = zones;
		this._dangerZones = new HashMap<AID, HashMap<Integer, DangerZone>>();
		MAXZONES = new HashMap<AID, Integer>();
		MAXZONESALL = 0;
		
		this.markDangerZones(this._maxZones);
		
		Drawer.addDangerZones(this.getEnemyDangerZones());
	}
	
	/**
	 * schreibt die Daten ins Hashmap.
	 * @param dangerZone
	 */
	private void writeData(DangerZone dangerZone) {
		if(this._dangerZones.containsKey(dangerZone._aid) == false) {
			HashMap<Integer, DangerZone> dangerZones = new HashMap<Integer, DangerZone>();
			dangerZones.put(dangerZone._move.getNode(), dangerZone);
			this._dangerZones.put(dangerZone._aid, dangerZones);
		} else {
			HashMap<Integer, DangerZone> dangerZones =  this._dangerZones.get(dangerZone._aid);
			
			for(Integer d : dangerZones.keySet()) {
				if(d == dangerZone._move.getNode()) {
					return;
				}
			}
			
			dangerZones.put(dangerZone._move.getNode(), dangerZone);
		}
		
		
		//trägt immer den aktuell größten Radius ein. (Spieler-speziefisch)
		if(MAXZONES.containsKey(dangerZone._aid) == false) {
			MAXZONES.put(dangerZone._aid, dangerZone._radius);
		}
		if(dangerZone._radius > MAXZONES.get(dangerZone._aid)) {
			MAXZONES.put(dangerZone._aid, dangerZone._radius);
		}
		
		//trägt immer den aktuell größten Radius ein. (Global für alle Spieler)
		if(dangerZone._radius > MAXZONESALL) MAXZONESALL = dangerZone._radius;
		
	}

	/**
	 * Heuristik für Nodes auf denen ein Detective steht.
	 */
	private void markDangerZones(int maxZones) {
		for(Entry<AID, Move> entry : Brain.HISTORYCAPTURER.getLastPlayerMoves()) {
			Move move = entry.getValue();
			AID aid = entry.getKey();
			if(move != null) {
				DangerZone dangerZone = new DangerZone(aid, move, 0);
				this.writeData(dangerZone);
					
				//Nachbarn
				this.markDangerNeigbourZones(aid, dangerZone, maxZones);
			}
		}
	}
	
	/**
	 * (Rekursiv) Heuristiken für Nodes die nah an einem Detective sind.
	 */
	private void markDangerNeigbourZones(AID aid, DangerZone dangerZone, int maxZones) {
		//Openlist/CloseList um zu verhindern das wieder ein Node was schon untersucht wurde nochmals untersucht wird.
		ArrayList<DangerZone> openList = new ArrayList<DangerZone>();
		ArrayList<DangerZone> closedList = new ArrayList<DangerZone>();

		openList.add(dangerZone);

		while(openList.size() != 0) {
			DangerZone currentDangerZone = openList.remove(0); //openList.size()-1);->Tiefensuche, 0 ->Breitensuche
			closedList.add(currentDangerZone);
			
			if(currentDangerZone._radius >= maxZones) {
				return;
			}
			
			Set<ScotlandJadeEdge> scotlandJadeEdges = this._map.edgesOf(this._map.get(currentDangerZone._move.getNode()));
			
			for(ScotlandJadeEdge scotlandJadeEdge : scotlandJadeEdges) {
				int connectionNodeIndex = scotlandJadeEdge.getTargetIndex(currentDangerZone._move.getNode());
				
				DangerZone isOnCloseList = this.isOnList(connectionNodeIndex, closedList);
				if(isOnCloseList == null) {
					int connectionTicket = scotlandJadeEdge.getType();
					DangerZone testDangerZone = new DangerZone(aid, new Move(connectionNodeIndex, connectionTicket), currentDangerZone._radius + 1);
					openList.add(testDangerZone);
					this.writeData(testDangerZone);
				}
				
			}

		}
	}

	/**
	 * prüft ob eine nodeIndex schon in eine DangerZone-List existiert. (Repeated-State Unterbindung)
	 * @param nodeIndex
	 * @param list
	 * @return
	 */
	private DangerZone isOnList(int nodeIndex, ArrayList<DangerZone> list) {
		for(int i = 0; i < list.size(); i++) {
			if(nodeIndex == list.get(i)._move.getNode()) {
				return list.get(i);
			}
		}
		return null;
	}
	
	
	public String toString() {
		String info = "";
		for(AID aid: this._dangerZones.keySet()) {
	       info += aid.getLocalName() + "\n";
	       HashMap<Integer, DangerZone> dangerZone = this._dangerZones.get(aid);
	       
	       for(Integer nodeIndex: dangerZone.keySet()) {
	    	   info += dangerZone.get(nodeIndex) + "\n";
	       }
	    }
		return info;
	}
	
	/**
	 * gibt alle Dangerzonen eines bestimmten Radiuses zurück.
	 * @param nodeIndex
	 * @return
	 */
	public ArrayList<DangerZone> getDangerZonesByRadius(int radius) {
		ArrayList<DangerZone> dangerZones = new ArrayList<DangerZone>();
		
		for(AID aid: this._dangerZones.keySet()) {
			HashMap<Integer, DangerZone> dangerZoneMap = this._dangerZones.get(aid);
			for(int nodeIndex: dangerZoneMap.keySet()) {
				DangerZone dangerZone = dangerZoneMap.get(nodeIndex);
				if(dangerZone._radius == radius) {
					dangerZones.add(dangerZone);
				}
			}
		}
		
		return dangerZones;
	}
	
	
	/**
	 * gibt alle Dangerzonen eines nodeIndexes zurück.
	 * @param nodeIndex
	 * @return
	 */
	public ArrayList<DangerZone> getDangerZonesByNodeIndex(int nodeIndex) {
		ArrayList<DangerZone> dangerZones = new ArrayList<DangerZone>();
		
		for(AID aid: this._dangerZones.keySet()) {
			HashMap<Integer, DangerZone> dangerZoneMap = this._dangerZones.get(aid);
			if(dangerZoneMap.containsKey(nodeIndex)) {
				dangerZones.add(dangerZoneMap.get(nodeIndex));
			}
		}
		
		return dangerZones;
	}
	
	/**
	 * gibt alle Dangerzonen eines Spielers zurück.
	 * @return
	 */
	public List<DangerZone> getDangerZonesByAid(AID aid) {
		ArrayList<DangerZone> dangerZones = new ArrayList<DangerZone>();
		
		HashMap<Integer, DangerZone> dangerZoneMap = this._dangerZones.get(aid);
		
		for(int nodeIndex: dangerZoneMap.keySet()) {
			dangerZones.add(dangerZoneMap.get(nodeIndex));
		}
		
		return dangerZones;
	}
	
	/**
	 * gibt alle Dangerzonen zurück.
	 * @return
	 */
	public List<DangerZone> getDangerZones() {
		List<DangerZone> dangerZones = new ArrayList<DangerZone>();
		
		for(AID aid: this._dangerZones.keySet()) {
			HashMap<Integer, DangerZone> dangerZoneMap = this._dangerZones.get(aid);
			for(int nodeIndex: dangerZoneMap.keySet()) {
				dangerZones.add(dangerZoneMap.get(nodeIndex));
			}
		}
		
		return dangerZones;
	}
	
	/**
	 * gibt alle Dangerzonen zurück.
	 * @return
	 */
	public List<DangerZone> getEnemyDangerZones() {
		ArrayList<DangerZone> dangerZones = new ArrayList<DangerZone>();
		
		List<AID> enemy = Brain.getEnemyAIDs();
		for(AID aid : enemy) {
			HashMap<Integer, DangerZone> dangerZoneMap = this._dangerZones.get(aid);
			
			for(int nodeIndex: dangerZoneMap.keySet()) {
				dangerZones.add(dangerZoneMap.get(nodeIndex));
			}
		}
		
		return dangerZones;
	}
	
	/**
	 * gibt alle die Nodes zurück die am weitesten weg sind vom Spieler.
	 * @param aid
	 * @return
	 */
	public List<DangerZone> getMaxValueDangerZones(AID aid) {
		List<DangerZone> farDangerZones = new ArrayList<DangerZone>();
		
		List<DangerZone> dangerZones = Brain.GRAPHANALYSER.getDangerZonesByAid(aid);
		for(int i = 0; i < dangerZones.size(); i++) {
			DangerZone dangerZone = dangerZones.get(i);
			if(dangerZone._radius == Brain.GRAPHANALYSER.getMaxZoneValue(aid)) {
				farDangerZones.add(dangerZone);
			}
		}
		return farDangerZones;
	}
	
	/**
	 * gibt die DangerZones mit dem maximalsten Radien aller Spieler zurück.
	 * @return
	 */
	public List<DangerZone> getMaxValueDangerZones() {
		List<DangerZone> farDangerZones = new ArrayList<DangerZone>();
		
		List<AID> enemy = Brain.getEnemyAIDs();		
		for(AID aid : enemy) {
			
			List<DangerZone> dangerZones = Brain.GRAPHANALYSER.getDangerZonesByAid(aid);
			for(int i = 0; i < dangerZones.size(); i++) {
				DangerZone dangerZone = dangerZones.get(i);
				if(dangerZone._radius == Brain.GRAPHANALYSER.getMaxZoneValue(aid)) {
					farDangerZones.add(dangerZone);
				}
			}
		
		}
		return farDangerZones;
	}
	
	/**
	 * prüft ob ein Node ein Feld ist wo der Spieler drauf steht oder durch ihn mit einem Zug erreichen.
	 * @param nodeIndex
	 * @return
	 */
	public Boolean isNodeSave(int nodeIndex) {
		List<DangerZone> dangerZones = this.getDangerZonesByNodeIndex(nodeIndex);
		for(DangerZone dangerZone : dangerZones) {
			if(dangerZone._aid.equals(Brain.getOwnAID()) == false) {
				if(dangerZone._radius == 0 || dangerZone._radius == 1) {
					return false;
				}
			}
		}
		return true;
	}
	
	/**
	 * gibt die maximale Tiefe zurück die verwendet wurde zur Ermittlung der Zonen.
	 * @return
	 */
	public int getMaxZonesAll() {
		return MAXZONESALL;
	}
	
	
	/**
	 * gibt den maximalen Radius eines Spielers zurück.
	 * @param aid
	 * @return
	 */
	public int getMaxZoneValue(AID aid) {
		if(MAXZONES.containsKey(aid)) {
			return MAXZONES.get(aid);
		}
		return -1;
		
	}
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
}
